from decimal import Decimal
from math    import *

class ICM:
    ''' The Independant Chip Model (ICM) in Poker is a way of representing your current equity in a 
        tournament prize pool based on the stack sizes of the remaining players and the payout structure.
        
        @see: http://www.pokerhelper.com/articles/independent-chip-modeling-part-1-derivation-and-sample-analysis/
        @see: 
        @see: 
    '''
    def __init__(self, total, stack, payout):
        self.stack = stack
        self.payout = payout
        self.total = total

    def calc_equity(self):
        retVal = []

        # start running the ICM equity recursion
        for i, s in enumerate(self.stack):
            e = self._equity(self.total, i, 0)
            retVal.append(round(e, 5))

        return retVal


    def _equity(self, total, player, depth):
        ''' recursive function
        '''
        retVal = Decimal(self.stack[player]) / total * self.payout[depth]
        if(depth + 1 < len(self.payout)):
            i = 0
            for s in self.stack:
                if i != player and s > 0.0:
                    self.stack[i] = 0.0
                    retVal += self._equity((total - s), player, (depth + 1)) * (s / Decimal(total))
                    self.stack[i] = s
                i += 1
        return retVal


    @classmethod
    def chop(cls, payout, stacks):
        ''' Calculate splitting the prize pool among N stacks with payout P
        '''
        retVal = []
        icm = ICM(sum(stacks), stacks, payout)
        equity = icm.calc_equity()
        for i, s in enumerate(stacks):
            retVal.append({'stack':s, 'equity':equity[i]})

        return retVal

    @classmethod
    def eliminate_player(cls, payout, total_chips, hero_chips_start, hero_chips_after, players):
        icm1 = ICM.make_icm_via_simple_equity(payout, total_chips, hero_chips_start, players)
        icm2 = ICM.make_icm_via_simple_equity(payout, total_chips, hero_chips_after, players - 1)
        pe1 = icm1.calc_equity()
        pe2 = icm2.calc_equity()

        gain = pe2[0] - pe1[0]
        odds = '{0:.0f}-to-1'.format(ceil(pe1[0] / gain))
        return {'start_equity':pe1[0], 'after_equity':pe2[0], 'gain':gain, 'odds':odds, 'chips_start':hero_chips_start, 'chips_after':hero_chips_start, 'players':players, 'all_equity_start':pe1, 'all_equity_after':pe2}

    @classmethod
    def call_allin(cls, payout, call_win, call_tie, call_lose, fold):
        ''' Calculate 

            Call and win: {2,500, 4,000, 3,500, 0} ($305.13)
            Call and tie: {1,500, 4,000, 3,500, 1,000} ($199.80)
            Call and lose: {500, 4,000, 3,500, 2,000} ($74.95)
            Fold: {1,300, 4,000, 3,500, 1,200} ($176.78)
        '''
        retVal = {}

        # call and win
        retVal['call_win'] = ICM.chop(payout, call_win)
        retVal['call_tie'] = ICM.chop(payout, call_tie)
        retVal['call_lose'] = ICM.chop(payout, call_lose)
        retVal['fold'] = ICM.chop(payout, fold)

        return retVal

    @classmethod
    def get_stacks_hero_bb(cls, payout, stacks, hero_pos, bb, sb, ante=0):
        '''
        sb_remainder = 0
        gain = hero_chips
        if hero_chips > villian_chips:
            gain = villian_chips + ante
        else:
            gain = hero_chips + ante
            villian_remainder = villian_chips - hero_chips
        '''
        retVal = {}
        retVal['call_win'] = 0
        retVal['call_tie'] = 0
        retVal['call_lose'] = 0
        retVal['fold'] = 0

        return retVal

    @classmethod
    def evaluate_monetaryEV(cls, payout, call_win, call_tie, call_lose, fold, win_odds, lose_odds, tie_odds, player=0):
        '''
            Is it better to call or fold in a certain situation...check the mEV
            @see: http://www.pokerhelper.com/articles/independent-chip-modeling-part-1-derivation-and-sample-analysis#Using The ICM to Evaluate the mEV of Decisions
        '''
        equities = ICM.call_allin(payout, call_win, call_tie, call_lose, fold)

        fe = equities['fold'][player]['equity']
        ce = equities['call_win'][player]['equity'] * win_odds + equities['call_lose'][0]['equity'] * lose_odds + equities['call_tie'][0]['equity'] * tie_odds
        cf = 'fold'
        if ce >= fe: cf = 'call'
        if ce >= (fe * 2): cf = 'easy call'
        retVal = {'fold_equity':fe, 'call_equity':ce, 'equities':equities, 'action':cf}
        return retVal

    @classmethod
    def make_icm_via_simple_equity(cls, payout, total_chips, hero_chips, remaining_players):
        ''' Builds an array of stacks based on one player (hero), total chips and remaining players
            using this, it calls into the ICM equity routine, and returns a 
        '''
        # add hero stack first, then estimate other stacks based on remaining chips
        # TODO: maybe add ability to specify other stacks
        stacks = [hero_chips]
        remaining_chips = total_chips - hero_chips
        i = 1
        while i < remaining_players:
            stacks.append(remaining_chips / (remaining_players - 1))
            i += 1

        icm = ICM(total_chips, stacks, payout)
        return icm


    #
    # Command Line Print Methods
    #

    @classmethod
    def chop_print(cls, payout, stacks):
        print '\n'
        chop = ICM.chop(payout, stacks)
        print 'With a prize pool of ${0:.2f} (and {1} chips in play)'.format(sum(payout), sum(stacks))
        for c in chop:
            print ' stack {0:4} gets ${1:.2f}'.format(c['stack'], c['equity'])

    @classmethod
    def eliminate_player_print(cls, payout, total_chips, hero_chips_start, hero_chips_after, players):
        ep = ICM.eliminate_player(payout, total_chips, hero_chips_start, hero_chips_after, players)
        print '\n'
        print 'hero risks ${0:.2f} equity to win ${1:.2f} by playing this hand'.format(ep["start_equity"], ep["gain"])
        print '(equal to a {0} bet against oneself)'.format(ep["odds"])


    @classmethod
    def equity_print(cls, payout, total_chips, hero_chips, remaining_players):
        icm = ICM.make_icm_via_simple_equity(payout, total_chips, hero_chips, remaining_players)
        player_equity = icm.calc_equity()
        print 'hero equity: $' + str(e[0])
        print 'other ' + str(remaining_players - 1) + ' players: $' + str(player_equity[1])
